#ifndef TRIGGER_STATE_H_INCLUDED
#define TRIGGER_STATE_H_INCLUDED
#include <stdio.h> 		// Needed before NIDAQmx.h with MinGW
#include "NIDAQmx.h"
#include <assert.h>
#include <iostream>
#include <sstream>
#include <iterator>

const long RISING = DAQmx_Val_RisingSlope;
const long FALLING = DAQmx_Val_FallingSlope;

const long GROUPBYSCAN = DAQmx_Val_GroupByScanNumber;
const long GROUPBYCHANNEL = DAQmx_Val_GroupByChannel;

class arrayHolder
{
public:
    arrayHolder(long channelGrouping, long samplesPerChannel, int numberOfChannels) :
        groupMethod(channelGrouping), pArray(new double[samplesPerChannel * numberOfChannels]), samplesPerChan(samplesPerChannel), numChans(numberOfChannels) {}
    arrayHolder(long channelGrouping, long samplesPerChannel, int numberOfChannels, double* initialData) :
        groupMethod(channelGrouping), pArray(new double[samplesPerChannel * numberOfChannels]), samplesPerChan(samplesPerChannel), numChans(numberOfChannels){
        for(long i = 0; i < samplesPerChannel * numberOfChannels; i++)
            pArray[i] = initialData[i];
    }
    ~arrayHolder() {delete[] pArray;}
    double* getData(){return pArray;}
    long getSize(){return samplesPerChan*numChans;}
    long getGroupMethod() {return groupMethod;}
    int getNumChans(){return numChans;}
    long getSampsPerChan() {return samplesPerChan;}
private:
    arrayHolder( const arrayHolder& rhs );
    arrayHolder& operator=( const arrayHolder& rhs );
    long    groupMethod;
    double* pArray;
    long    samplesPerChan;
    int     numChans;
};
namespace AddressCalculatorHelper
{
    inline double* addrByScan(double* offset, int numChannels, long index){return offset + numChannels*index;}
    inline double* addrByChannel(double* offset, int numChannels, long index){return offset + index;}
};

class AddressCalculator
{
public:
    AddressCalculator(arrayHolder& dh, int channel) : offset(0), numChans(dh.getNumChans()), addr(0)
    {
        if (dh.getGroupMethod() == GROUPBYSCAN)
        {
            offset = dh.getData() + channel;
            addr = &AddressCalculatorHelper::addrByScan;
        }
        else
        {
            offset = dh.getData() + channel * dh.getSampsPerChan();
            addr = &AddressCalculatorHelper::addrByChannel;
        }
    }
    inline double* operator()(long index){return addr(offset, numChans, index);}
private:
    double* offset;
    int numChans;
    double* (*addr)(double*, int, long);
};

//class ChanPointer : public std::iterator<std::input_iterator_tag, arrayHolder>
class ChanPointer
{
public:
    ChanPointer(arrayHolder& dh, int channelNumber) :
        addrCalc(dh, channelNumber), pointerIndex(0) {if (false){std::cout << "ctor chan: "<< channelNumber << std::endl;}}
    ~ChanPointer() {if (false) {std::cout << "Dtor "<< std::endl;}}
    double& operator[](long index) {return *addrCalc(pointerIndex + index);}
    ChanPointer operator++(){++pointerIndex; return *this;}
    ChanPointer operator++(int){ChanPointer temp = *this; pointerIndex++; return temp;}
    double& operator*(){return *addrCalc(pointerIndex);}
    ChanPointer operator+(long val) {pointerIndex += val; return *this;}
    bool operator< (ChanPointer& rhs) {return addrCalc(pointerIndex) < rhs.addrCalc(rhs.pointerIndex);}
    long operator- (ChanPointer& rhs) {return pointerIndex - rhs.pointerIndex;}
    bool operator!= (ChanPointer& rhs) {return addrCalc(pointerIndex) != rhs.addrCalc(rhs.pointerIndex);}
    bool operator== (ChanPointer& rhs) {return addrCalc(pointerIndex) == rhs.addrCalc(rhs.pointerIndex);}
//    operator long(){return long(addrCalc(pointerIndex));)
private:
    AddressCalculator addrCalc;
    long pointerIndex;
};

class TriggerState //{
//! \brief Abstract base class for all trigger states
//!
//! Update member function checks new data to see if
//! trigger state should change.  The tempIndex variable is meaningful
//! \b only for the block of data in which it is set and \b only for the trigger state
//! which set it.
{
public:
    TriggerState() {tempIndex =-1;}
    virtual ~TriggerState() {};
    virtual const std::string  getName() const = 0;
    bool operator==(const TriggerState &other)const;

    //! \brief May changes class of trigger state if new samples statisfy condition
    //!
    //! \param sampsPerChanRead Samples per channel read from the DAQ into beginning of test buffer overrighting old data
    //! \return The current trigger state class instance
    //!
    TriggerState* update(long sampsPerChanRead);
    virtual TriggerState* updateInstance(long sampsPerChanRead, long startIndex) = 0;
    long getTempIndex(){return tempIndex;}
    void setTempIndex(long value){ tempIndex = value;}

private:
    static long tempIndex;     //!< Data index first data element next trigger state should test
}; //}

//! Global functions for printing trigger states
std::ostringstream& operator<<(std::ostringstream& out, const TriggerState &state);
std::ostream& operator<<(std::ostream& out, const TriggerState &state);

class PreTrigFilling : public TriggerState
//! \brief Trigger state in which the number of samples is less than the requested number of pre trigger samples.
//!
//! Should be the initial trigger state.
//!
{
public:
    PreTrigFilling(TriggerState* pFull, long preTrigSampsPerChan) : pPreTrigFull(pFull),
            preTrigSampsPerChan(preTrigSampsPerChan), availableSampPerChan(0){}

    virtual const std::string  getName()const {return "PreTrigFilling";}
    virtual TriggerState* updateInstance(long sampsPerChanRead, long startIndex);
    inline long getAvailableSampPerChan() {return availableSampPerChan;}
//DAta members
private:
    TriggerState* pPreTrigFull;
    long preTrigSampsPerChan;
    long availableSampPerChan;
};

class VoltageLevelLogic
{
public:
    VoltageLevelLogic(long trigSlope, double voltageLevel, ChanPointer pDataBuffer);
    long operator()(long sampsPerChanRead, long index);

    long slope;
    double level;
    ChanPointer pData;
    bool (*pFun)(double, double);
};

class PreTrigFull : public TriggerState
{
public:
    PreTrigFull(TriggerState* pArmedState, long triggerSlope, double armVoltage, ChanPointer pData);
    virtual const std::string  getName()const {return "PreTrigFull";}
    virtual TriggerState* updateInstance(long sampsPerChanRead, long startIndex);
    inline double getArmingLevel() {return arm.level;}
    inline long getArmingSlope() {return arm.slope;}

private:
    TriggerState*   pArmed;
    VoltageLevelLogic arm;

};

class Armed : public TriggerState
{
public:
    Armed(TriggerState* pTriggeredClass, long triggerSlope, double triggerVoltage, ChanPointer pData) :
        TriggerState(), pTriggered(pTriggeredClass), trig(triggerSlope, triggerVoltage, pData) {};

    virtual const std::string  getName()const {return "Armed";}
    virtual TriggerState* updateInstance(long sampsPerChanRead, long startIndex);
    inline double getTrigLevel() {return trig.level;}
    inline long getTrigSlope() {return trig.slope;}

private:
    TriggerState*   pTriggered;
    VoltageLevelLogic trig;  // must rename class!!
};

class Triggered : public TriggerState
{
public:
    Triggered() {};
    virtual const std::string  getName()const {return "Triggered";}
    virtual TriggerState* updateInstance(long sampsPerChanRead, long startIndex){
 //       assert(false);      // should not call update after being triggered
        return this;
    }
};

class TriggerContext
{
public:
    TriggerContext(long preTriggerSamples,
                   long postTriggerSamples,         // needed
                   long triggerSlope,
                   double triggerLevel,
                   double armingLevel,
                   ChanPointer sampleBuffer,
                   long numberOfChannels,           //needed
                   long bufferSize)
        :tsDone(), tsArmed(&tsDone, triggerSlope, triggerLevel, sampleBuffer),
         tsFull(&tsArmed, triggerSlope, armingLevel, sampleBuffer),
         tsFilling(&tsFull, preTriggerSamples), pState(&tsFilling) {}
    //! Test this convience function!
    //!
    bool IsTriggered(long samplesPerChanReadIn){
        if (&tsDone == (pState = pState->update(samplesPerChanReadIn))) {
            readPastTrig = pState->getTempIndex();
            return true;
        }
        return false;
    ;}
    Triggered tsDone;
    Armed tsArmed;
    PreTrigFull tsFull;
    PreTrigFilling tsFilling;
    TriggerState* pState;
    long readPastTrig;
};


#endif // TRIGGER_STATE_H_INCLUDED
